package jpf.nextsearch.resourceprovider.impl;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import jpf.nextsearch.base.GenericObject;
import jpf.nextsearch.resourceprovider.ResourceProvider;
import jpf.nextsearch.resourceprovider.handler.ResourceHandler;
import jpf.nextsearch.resourceprovider.model.Resource;

import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;

/**
 * @author <a href="mailto:jacek.pruszkowski@gmail.com">Jacek Pruszkowski</a>
 */
public class FtpResourceProvider extends GenericObject implements ResourceProvider {

	private String host;
	private int port;
	private boolean useBinary;
	private boolean usePassive;
	private String user;
	private String pass;
	private int connectTimeout;

	private FTPClient ftpClient;

	public FtpResourceProvider(String host, int port, String user, String pass, boolean useBinary, boolean usePassive, int connectTimeout) {
		super();
		this.host = host;
		this.port = port;
		this.useBinary = useBinary;
		this.usePassive = usePassive;
		this.user = user;
		this.pass = pass;
		this.connectTimeout = connectTimeout;
	}

	public boolean close() {
		try {
			ftpClient.abort();
			ftpClient.logout();
			ftpClient.disconnect();
			return true;
		} catch (Exception e) {
			if (isLoggingError()) {
				getLogger().error("Exception occoured", e);
			}
			e.printStackTrace();
		}
		return false;
	}

	public boolean connect() {
		boolean error = false;
		try {
			ftpClient = new FTPClient();
			ftpClient.setConnectTimeout(connectTimeout);
			ftpClient.setDefaultPort(port);
			System.out.println("connecting ");
			ftpClient.connect(host);
			System.out.println("connected ");
			int reply = ftpClient.getReplyCode();
			if (!FTPReply.isPositiveCompletion(reply)) {
				ftpClient.disconnect();
				error = true;
				if (isLoggingError()) {
					getLogger().error("Received negative reply: " + reply);
				}
				return false;
			}
			if (!ftpClient.login(user, pass)) {
				ftpClient.logout();
				error = true;
				if (isLoggingError()) {
					getLogger().error("Ftp login failed");
				}
				return false;
			}
			if (useBinary) {
				ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
			}
			if (usePassive) {
				ftpClient.enterLocalPassiveMode();
			}
			return true;
		} catch (Exception e) {
			System.out.println(ftpClient.getReplyString());
			if (isLoggingError()) {
				getLogger().error("Exception occoured", e);
			}
			e.printStackTrace();
			error = true;
		} finally {
			if (error && ftpClient != null && ftpClient.isConnected()) {
				try {
					ftpClient.disconnect();
				} catch (Exception e) {
					if (isLoggingError()) {
						getLogger().error("Exception occoured during disconnecting", e);
					}
				}
			}
		}

		return false;
	}

	public boolean isConnected() {
		return ftpClient != null && ftpClient.isConnected();
	}

	@Override
	public List<Resource> listResources() throws Exception {
		return listResources(0, null, null);
	}

	@Override
	public void passResourcesToHandler(int max, ResourceHandler handler, Object params) throws Exception {
		List<Resource> resources = listResources(max, handler, params);
		handler.handleResources(resources, params);
	}

	private List<Resource> listResources(int handleSize, ResourceHandler handler, Object params) throws Exception {

		if (!isConnected()) {
			throw new IllegalStateException("Ftp client is not connected !");
		}

		ftpClient.changeWorkingDirectory("/");
		List<Resource> resources = new ArrayList<Resource>();
		Set<String> visitedDirs = new HashSet<String>();
		getFilesFromDir("/", resources, visitedDirs, handleSize, handler, params);
		return resources;
	}

	private void getFilesFromDir(String dir, Collection<Resource> resources, Collection<String> visitedDirs, int handleSize,
			ResourceHandler handler, Object params) throws IOException {
		if (visitedDirs.contains(dir)) {
			return;
		}
		ftpClient.changeWorkingDirectory(dir);
		visitedDirs.add(dir);
		FTPFile[] filesArray = ftpClient.listFiles();
		for (FTPFile f : filesArray) {
			if (f.isFile()) {
				resources.add(new Resource(dir, f.getName(), f.getSize()));
				if (handler != null && resources.size() >= handleSize) {
					handler.handleResources(resources, params);
					resources.clear();
				}
			} else if (f.isDirectory() && !"..".equals(f.getName()) && !".".equals(f.getName())) {
				String newDir = dir.endsWith("/") ? dir + f.getName() : (dir + "/" + f.getName());
				getFilesFromDir(newDir, resources, visitedDirs, handleSize, handler, params);
			}
		}
	}

}
